package com.yuanian.timingwheel.model.timingwheel;

import com.yuanian.timingwheel.context.ExecutionContext;
import com.yuanian.timingwheel.infrastructure.DateUtils;
import com.yuanian.timingwheel.model.task.base.BaseTask;
import com.yuanian.timingwheel.model.task.method.TaskMethod;
import com.yuanian.timingwheel.model.timingwheel.helper.DbHelper;
import com.yuanian.timingwheel.respository.po.TaskEntity;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 日级时间轮
 */
public class DayTimingWheel {

    //一天有24*60分钟
    private int minuteNum = 24 * 60;

    //秒级时间轮
    private Map<Integer, SecondTimingWheel> secendTimeWheels;

    //启动标识
    private volatile boolean startFlag = false;

    //核心线程
    private Thread mainThread;

    //当前小时
    private int hour = 0;

    //当前分钟
    private int minute = 0;

    //当前时间秒针
    private int second = 0;

    //已经加载了任务的日期集合
    private Set<Long> loadDbDateSet;

    //最后一次运行时间,供查看是否在运行
    private Date lastRespondTime;

    //卡顿总秒数
    private long delayCnt = 0;

    //存活的机器标识
    private Long livingServerId;

    public DayTimingWheel() {

        secendTimeWheels = new HashMap(minuteNum);

        loadDbDateSet = new HashSet<>();

        mainThread = new Thread(new Runnable() {
            @Override
            public void run() {
                //当日的任务是否加载,初次启动需加载当日数据, 初次启动需要把当日以前没运行的任务先运行完了,
                //锁住当日，执行加载任务
                livingServerId = DbHelper.lockDay(() -> {
                    loadTask();
                });
                //没有获取运行的权利
                if (livingServerId == null) {
                    return;
                }
                init();
                while (startFlag) {
                    //当前该运行的分钟
                    lastRespondTime = new Date();
                    int passedMinute = (int)((lastRespondTime.getTime() - DateUtils.zeroDay()) / 60000);
                    hour = passedMinute / 60;
                    minute = passedMinute - hour * 60;
                    //凌晨0点0分钟加载任务
                    if (hour == 0 && minute == 0) {
                        loadTask();
                    }
                    //获取当前的秒钟
                    int now = DateUtils.getSecond();
                    if (now != second) {//秒推进
                        doSecondChange(passedMinute, now);
                    } else {
                        doSecondNoChange(passedMinute, now);
                    }
                }
            }
        });
    }

    /**
     * 运行前的初始化
     */
    private void init() {
        ExecutionContext.getInstance().setDayTimingWheel(this);
    }
    /**
     * 秒不变的业务逻辑
     * @param restTime
     * @param now
     */
    private void doSecondNoChange(int restTime, int now) {

        if (DateUtils.restSecond() > 200) {
            //更新存活信息
            //补偿执行前面的任务
            for (int i = 2; i <= restTime; i++) {
                //1秒快到期了,退出下分钟当前秒再补偿
                if (DateUtils.restSecond() < 800) {
                    break;
                }
                int round = restTime - i;
                SecondTimingWheel secondTimingWheel = getSecondTimingWheel(round);
                if (secondTimingWheel != null && !secondTimingWheel.isCompleted(now)) {
                    secondTimingWheel.trigger(now);
                }
            }
        }
        //让出CPU
        try {
            TimeUnit.MILLISECONDS.sleep(DateUtils.restSecond());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 秒推进的业务逻辑
     * @param restTime
     * @param now
     */
    private void doSecondChange(int restTime, int now) {
        System.out.println("当前时间为:" + hour + "小时" + minute + "分钟" + now + "秒");
        //间隔时间
        int num = now - second;
        if (num < 1) {//秒钟走到了0秒
            num = 60 + now - second;
        }
        SecondTimingWheel secondTimingWheel = null;
        for (int i = 0; i < num; i++) {
            if (i != 0) {//卡顿,i!=0说明卡顿了
                delayCnt++;
            }
            //推进秒钟,可补偿没有运行的秒钟
            int sec = now - i;
            if (sec >= 0) {
                secondTimingWheel = getSecondTimingWheel(restTime);
                ExecutionContext.getInstance().setSecondTimingWheel(secondTimingWheel);
                if (secondTimingWheel != null) {
                    secondTimingWheel.trigger(sec);
                }
            } else {
                secondTimingWheel = getSecondTimingWheel(restTime - 1);//上一分钟
                ExecutionContext.getInstance().setSecondTimingWheel(secondTimingWheel);
                if (secondTimingWheel != null) {
                    secondTimingWheel.trigger(sec + 60);
                }
            }
        }
        //当前秒很空闲，挤出点时间更新一下响应结果信息
        if (DateUtils.restSecond() > 100) {
            DbHelper.updateLivingServer(this.livingServerId, this.lastRespondTime, this.delayCnt);
        }
        //设置当前秒
        second = DateUtils.getSecond();
    }

    /**
     * 加载当日的任务
     */
    private void loadTask() {
        if (!loadDbDateSet.contains(DateUtils.zeroDay())) {
            loadDbDateSet.add(DateUtils.zeroDay());
            loadRequiredRunTask();
        }
    }

    /**
     * 加载当天12点以前执行的任务
     */
    private void loadRequiredRunTask() {
        //获取当前23:59:59之前的任务
        List<TaskEntity> list = DbHelper.getEndOfDayTask();
        for (TaskEntity taskEntity : list) {
            BaseTask task = new BaseTask(taskEntity.getBusinessId(), taskEntity.getExecutionInterval(), taskEntity.getExecutionRounds(), taskEntity.getExecutedRounds(), taskEntity.getExecutionTime(), taskEntity.getTaskDesc());
            task.setTaskMethod(new TaskMethod(taskEntity.getTaskMethod().getExecutionMethod(), taskEntity.getTaskMethod().getBeanName(), taskEntity.getTaskMethod().getArgs()));
            task.setId(taskEntity.getId());
            loadTask(task);
        }
    }

    /**
     * 增加任务
     * @param task
     */
    public void addTask(BaseTask task) {
        save(task);
        loadTask(task);
    }

    /**
     * 从数据库装载任务
     * @param task
     */
    public void loadTask(BaseTask task) {
        //得到任务分钟盘
        Integer round = getRound(task);
        if (round != null && round >= 0) {
            getAndSetSecondTimingWheel(round).addTask(task, round);
        } else if (round < 0) {//过期任务，马上执行
            task.execute();
        }
    }

    /**
     * 保存任务到数据库
     * @param task
     */
    public void save(BaseTask task) {
        TaskEntity.save(task);
    }

    private SecondTimingWheel getAndSetSecondTimingWheel(int round) {
        if (round < 0 && round > (minuteNum - 1)) {
            return null;
        }
        SecondTimingWheel secondTimingWheel = secendTimeWheels.get(round);
        if (secondTimingWheel == null) {
            secondTimingWheel = new SecondTimingWheel(second);
            secendTimeWheels.put(round, secondTimingWheel);
        }
        return secondTimingWheel;
    }

    private SecondTimingWheel getSecondTimingWheel(int round) {
        if (round < 0 && round > (minuteNum - 1)) {
            return null;
        }
        return secendTimeWheels.get(round);
    }

    /**
     * 获取任务的轮次
     * @param task
     * @return
     */
    private Integer getRound(BaseTask task) {
        long executionTime = task.getTaskInfo().getExecutionTime().getTime();
        //和当天0点相比较,获取间隔的分钟数
        Long restTime = (executionTime - DateUtils.zeroDay()) / 60000;
        if (restTime >= minuteNum) {//不是当前的任务
            return null;
        }
        if (restTime < 0) {
            return -1;
        }
        return restTime.intValue();
    }

    public void start() {
        startFlag = true;
        mainThread.start();
    }

    public void suspend() {
        if (startFlag) {
            startFlag = false;
        }
    }

    public void resume() {
        if (!startFlag) {
            startFlag = true;
        }
    }

    public void shutdown() {
        suspend();
        mainThread = null;
    }

    public static void main(String[] args) {
        HashMap h = new HashMap();
        System.out.println(h.put(1,1));
    }

    public boolean isStartFlag() {
        return startFlag;
    }

    public void setStartFlag(boolean startFlag) {
        this.startFlag = startFlag;
    }

    public int getHour() {
        return hour;
    }

    public void setHour(int hour) {
        this.hour = hour;
    }

    public int getMinute() {
        return minute;
    }

    public void setMinute(int minute) {
        this.minute = minute;
    }

    public int getSecond() {
        return second;
    }

    public void setSecond(int second) {
        this.second = second;
    }

    public Date getLastRespondTime() {
        return lastRespondTime;
    }

    public void setLastRespondTime(Date lastRespondTime) {
        this.lastRespondTime = lastRespondTime;
    }

    public long getDelayCnt() {
        return delayCnt;
    }

    public void setDelayCnt(long delayCnt) {
        this.delayCnt = delayCnt;
    }
}
